package aufagabe2.master;

import static akka.actor.Actors.remote;

import java.math.BigInteger;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;

import javax.swing.JOptionPane;

import akka.actor.ActorRef;
import akka.actor.UntypedActor;
import akka.remoteinterface.RemoteServerModule;
import aufgabe2.worker.CalculateMessage;
import aufgabe2.worker.ResultMessage;
import aufgabe2.worker.TerminateMessage;
import aufgabe2.worker.Worker;
import static akka.actor.Actors.poisonPill;

public class Master extends UntypedActor {
	static int nbMaxWorkers;
	static RemoteServerModule remoteSupport;
	private static ActorRef newworker;
	private static GUIdata newworkerdata;
	private static ArrayList<ActorRef> listworkers=new ArrayList<ActorRef>();
	private static ArrayList<GUIdata> workersdata=new ArrayList<GUIdata>();
	private static ActorRef master;
	private BigInteger toFactorize;
	private static GUI window;
	private static long begintime;
	private static int id;
	private static int port = 2553;
	
	public void onReceive(Object message) throws Exception {
		if (message instanceof CalculateMessage) 
		{
			CalculateMessage calculate = (CalculateMessage) message;
			ActorRef worker = remote().actorFor(Worker.class.getName(),
			"workerserver", 2552);
			//getContext() returns a reference of this actor
			ActorRef me = getContext();
			//tell sends a message to an actor. Additionally, you can
			//pass a reference to another actor.
			worker.tell(calculate, me);
		} 
		else if (message instanceof ResultMessage)//a factor was found
		{
			ResultMessage rm = (ResultMessage) message;
			if(rm.isEnded()) //factorization is finished
			{
				Collections.sort(rm.getPrimeFactor());
				long time = System.currentTimeMillis() - begintime;
				String res=(rm.getPrimeFactor().toString());
				window.setLabelsResults(res,String.valueOf(rm.getCpuTime())+" ms",
						String.valueOf(rm.getSumRhoCycle()),String.valueOf(time)+" ms");
				for( ActorRef actor : listworkers )// Poison all Workers
				{
					actor.tell(poisonPill());
				}
			}
			else//factorization is not finished
			{
				if(id == rm.getCalculId()) //to be sure that a result is not treated two times
				{
					id++;
					ActorRef me = getContext();
					rm.setCalculId(id);
					//distribution of the factors to the workers
					for (ActorRef actor : listworkers){
							if(actor.isRunning())
								actor.tell(rm, me);
						}
				}
			}
				
		}
		else if (message instanceof GUIdata) //the message is coming from the GUI
		{
			GUIdata mess = (GUIdata) message;
			
			if(mess.getTypeOf().equals("addWorker"))//the button "Add Worker" was clicked
			{
			
				try{
					if(listworkers.size() < nbMaxWorkers) //we check if the max of number of workers was not reached
					{
						//we check if the worker was not already added 
						if(!isInList(mess)){
							newworkerdata = new GUIdata(mess.getAddress(), mess.getPort());
							newworker = remote().actorFor(Worker.class.getName(), 
							mess.getAddress(), mess.getPort());
							ActorRef me = getContext();
							newworker.tell(new DataMessage(mess.getAddress(), mess.getPort()),me);
						}
						else
							System.out.println("This worker was already added");
					}
					else
						System.out.println("There are already"+ nbMaxWorkers + "workers");
	
				}catch(Exception e){
					System.out.println("Exception: problem with the new worker");
					new JOptionPane().showMessageDialog(null, "Connection problem with the worker","Error", JOptionPane.ERROR_MESSAGE);
					newworker.tell(poisonPill());
					newworker = null;
				}
			}
			else if(mess.getTypeOf().equals("factorize"))//the button "Factorize" was clicked
			{
				id = 1;
				if( listworkers.size() > 0 ){
					begintime = System.currentTimeMillis();
					toFactorize = mess.getToFactorize();
					CalculateMessage calculateMessage = new CalculateMessage(toFactorize,workersdata,id);
					System.out.println("Number: " + mess.getToFactorize().toString());
					ActorRef me = getContext();
					for( ActorRef actor : listworkers )
						if(actor.isRunning())
							actor.tell(calculateMessage, me);
				}
			}
			else if(mess.getTypeOf().equals("delWorker"))//the button "Delete Worker" was clicked
			{
				try{
					if(removeWorker(mess))
						System.out.println("Worker removed");
					else
						System.out.println("Problem: the worker doesn't exist");
				}catch(Exception e){
					System.out.println("Exception with the remove of the worker");
				}
			}
			else if(mess.getTypeOf().equals("stopFact"))//the button "Stop Factorization" was clicked
			{
				for( ActorRef actor : listworkers ){
					actor.tell(new TerminateMessage(),master);
					//actor.exit();
				}
			}
		
		}
		else if (message instanceof DataMessage)//to add a new worker
		{
			workersdata.add(newworkerdata);
			listworkers.add(newworker);
			System.out.println("Worker added");
			addNewWorkerToLabels(workersdata);
			
		}
		else if (message instanceof TerminateMessage) //the factorization has been stopped
		{
			TerminateMessage mess = (TerminateMessage) message;
			long time = System.currentTimeMillis() - begintime;
			window.setLabelsResults("",String.valueOf(mess.cpuTime)+" ms",
					String.valueOf(mess.sumrho),String.valueOf(time)+" ms");
			
		}
		else 
		{
			throw new IllegalArgumentException("Unknown message [" +
			message + "]");
		}
	}
	
	public void addNewWorkerToLabels(ArrayList<GUIdata> workersData)
	{
		window.setListWorkers(workersData);
	}
	

	/**
	 * @param data
	 * @return true if the data is in workersdata, false otherwise
	 */
	public boolean isInList(GUIdata data)
	{
		for(GUIdata d : workersdata)
		{
			if(d.equals(data))
				return true;
		}
		return false;
	}
	
	public boolean removeWorker(GUIdata data)
	{
		int index;
		index = workersdata.indexOf(data);
		if(index>=0)
		{
			workersdata.remove(data);
			listworkers.remove(index);
			window.setListWorkers(workersdata);
			return true;
		}
		return false;
	}
	
	public static void main(String[] args) throws Exception{
		//The client must be started as a Remote-Actor too
		//to be able to receive messages from the Server
		remoteSupport = remote().start(InetAddress.getLocalHost().getHostAddress(), 2553);
		master = remote().actorFor(Master.class.getName(),InetAddress.getLocalHost().getHostAddress(), port);
		System.out.println("Master connected on port " + port);
		window=new GUI(master);
		nbMaxWorkers=window.getNbworker();
		window.setVisible(true);
	}
}
